import {ConflictException, Injectable, Logger} from "@nestjs/common";
import {Neo4jService} from "nest-neo4j/dist";
import {flattenObject} from "../../../utils/utils"

export type RelationshipConfig = {
    nodeFrom: string, // 关系头部节点
    fromWhere?: object, // 头部节点的属性,用于查找节点
    nodeTo: string, // 关系尾部节点
    toWhere?: object // 尾部节点的属性,用于查找结点
    relationship?: string // 节点关系
    shipProperty?: object // 节点的属性,可以不设置
    orderBy?: {
        node: [string, string], // 排序规则，0 排序的节点，1 节点的属性
        desc: boolean // 是否倒序
    }
}

@Injectable()
export class Neo4jDao {
    private readonly logger = new Logger(Neo4jDao.name);

    constructor(
        private readonly neo4jService: Neo4jService
    ) {
    }

    /**
     * @Description: 执行读取cql,
     * @author zhaojiuyi
     * @date 2023/4/10
     * @param {string} CQL 要执行的cql
     * @param {string[]} args 要获取的节点
     * @return {object} 如果只获取一个节点,则返回数组
     * 如果返回多个节点,则返回对象,通过传入的节点名取出数组
     */
    async read(CQL: string, ...args: string[]): Promise<any> {
        this.logger.log(CQL);
        const result = await this.neo4jService.read(CQL);
        return this.getResult(result, args);
    }

    /**
     * @Description: 执行写入cql
     * @author zhaojiuyi
     * @date 2023/4/10
     * @param {string} CQL 要执行的cql
     * @param {string[]} args 要获取的节点
     * @return {object} 如果只获取一个节点,则返回数组
     * 如果返回多个节点,则返回对象,通过传入的节点名取出数组
     */
    async write(CQL: string, ...args: string[]): Promise<any> {
        this.logger.log(CQL);
        const result = await this.neo4jService.write(CQL);
        return this.getResult(result, args);
    }

    /**
     * @Description: 获取CQL执行的结果
     * @author zhaojiuyi
     * @date 2023/4/10
     */
    getResult(CQLResult: any, args: string[]): object | object[] {
        if (args.length === 1) return CQLResult.records.map(record => flattenObject(record.get(args[0])));
        const tempObject = {};
        for (const arg of args) {
            tempObject[arg] = CQLResult.records.map(record => flattenObject(record.get(arg)));
        }
        return tempObject;
    }

    /*分页获取某类节点*/
    async getNodeListByPage(nodeName: string, pageNo: number, pageSize: number) {
        const start = pageNo * pageSize;
        const CQL = `MATCH (u:${nodeName}) RETURN u SKIP ${start} LIMIT ${pageSize}`;
        return this.read(CQL, "u");
    }

    /*根据条件查找所有节点*/
    async findNode(nodeName: string, where: object) {
        const temp = serialize(where);
        const CQL = `
            MATCH (n:${nodeName} ${temp})
            RETURN n`;
        return this.read(CQL, "n");
    }

    /*根据条件删除节点*/
    async deleteNode(nodeName: string, where: object) {
        const temp = serialize(where);
        const CQL = `
            MATCH (n:${nodeName} ${temp})
            DELETE n`;
        return this.write(CQL, "n");
    }

    /*根据条件删除节点之间的关系*/
    async deleteRelationship(config: RelationshipConfig) {
        const {nodeFrom, nodeTo, fromWhere, toWhere, relationship} = config;
        const tempFrom = serialize(fromWhere);
        const tempTo = serialize(toWhere);
        const CQL = `
            MATCH (a:${nodeFrom} ${tempFrom})-[r:${relationship}]->(b:${nodeTo} ${tempTo})
            DELETE r RETURN a,b`;
        return this.write(CQL, "a", "b");
    }

    /**
     * @Description: 查询节点之间与关系
     * @author zhaojiuyi
     * @date 2023/4/11
     * @return {
     *   a: 头节点
     *   b: 尾部节点
     *   r: 关系
     * }
     */
    async getRelationship(config: RelationshipConfig): Promise<{ a: any, b: any, r: any }> {
        const {nodeFrom, nodeTo, fromWhere, toWhere, relationship, orderBy} = config;
        const tempFrom = serialize(fromWhere);
        const tempTo = serialize(toWhere);
        const tempOrderBy = orderBy ? `ORDER BY 
            ${getOrderByNode(orderBy.node[0])}.${orderBy.node[1]} ${orderBy.desc ? "DESC" : ""}` : "";
        const CQL = `
            MATCH (a:${nodeFrom} ${tempFrom})-[r${relationship ? (":" + relationship) : ""}]->(b:${nodeTo} ${tempTo})
            RETURN a,r,b ${tempOrderBy}`;
        return this.read(CQL, "a", "r", "b");

        function getOrderByNode(nodeName) {
            const obj = {
                [nodeFrom]: "a",
                [nodeTo]: "b",
                [relationship]: "r"
            }
            return obj[nodeName]
        }
    }

    /*创建节点*/
    async createNode(nodeName: string, attributes: object) {
        const property = serialize(attributes);
        const CQL = `CREATE (u:${nodeName} ${property}) RETURN u`;
        try {
            return this.write(CQL, "u");
        } catch (e) {
            throw new ConflictException("节点创建失败");
        }
    }


    /*保存两个节点的关系,根据节点id*/
    async saveRelationship(config: RelationshipConfig) {
        const {nodeFrom, nodeTo, fromWhere, toWhere, relationship, shipProperty} = config;
        const tempFrom = serialize(fromWhere);
        const tempTo = serialize(toWhere);
        const set = serialize(shipProperty);

        // 查询是否存在要保存的关系,如果存在,则进行更新,否则创建
        const haveRelationShip = await this.getRelationship(config);
        const CQLInsert = `
            MATCH (b:${nodeFrom} ${tempFrom}),(u:${nodeTo} ${tempTo})
            CREATE (b)-[r:${relationship}]->(u)`
            + (shipProperty ? `SET r=${set}` : "")
            + `RETURN r`;
        const CQLUpdate = `
            MATCH (b:${nodeFrom} ${tempFrom})-[r:${relationship}]->(u:${nodeTo} ${tempTo})
            ${set ? "SET r=" + set : ""}
            RETURN r`;
        const CQL = haveRelationShip["r"].length ? CQLUpdate : CQLInsert;
        return this.write(CQL, "r");
    }


}

/**
 * @Description: 对象序列化
 * @author zhaojiuyi
 * @date 2023/4/10
 * @param {object} object
 * @return {string}
 * @example {
 *   {name:'zhaojiuyi',age:18} => "{name:'zhaojiuyi',age:'18'}"
 * }
 */
function serialize(object: Object): string {
    if (!object || (object && !Object.keys(object).length)) return "";
    let tempObjStr = "{";
    for (const key in object) {
        tempObjStr += `${key}:'${object[key]}',`;
    }
    return tempObjStr.slice(0, -1) + "}";

}


